﻿using System;
using System.ComponentModel;
using System.Windows.Input;
using MugenMvvmToolkit.Infrastructure;
using MugenMvvmToolkit.Interfaces;
using MugenMvvmToolkit.Interfaces.ViewModels;
using MugenMvvmToolkit.Interfaces.Views;
using MugenMvvmToolkit.Models;
using MugenMvvmToolkit.Utils;

#if TASKSUPPORT
using System.Threading.Tasks;
#endif

namespace MugenMvvmToolkit.ViewModels
{
    /// <summary>
    ///     Represents the base class for dialog view-models.
    /// </summary>
    public abstract class WindowViewModelBase<T> : ViewModelBase, IWindowViewModelBase where T : class, IWindowViewBase
    {
        #region Fields

        /// <summary>
        /// Gets the closeable view model facade.
        /// </summary>
        private readonly CloseableViewModelFacade _closeableViewModelFacade;
        private bool _shouldClose;
        private string _displayName;
        private ResultCallback<bool> _onClosed;
        private bool _isOpen;
        private CancelEventArgs _cancelArgs;
        private bool? _operationResult;

        #endregion

        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="WindowViewModelBase{T}" /> class.
        /// </summary>
        protected WindowViewModelBase()
        {
            _closeableViewModelFacade = GlobalSettings.GetCloseableFacade(this, CanClose, OnClosing, OnClosed);
        }

        #endregion

        #region Implementation of interfaces

        /// <summary>
        ///     Gets or sets the display name for the current <see cref="IWindowViewModel" />.
        /// </summary>
        public virtual string DisplayName
        {
            get { return _displayName; }
            set
            {
                if (value == _displayName) return;
                _displayName = value;
                OnPropertyChanged("DisplayName");
            }
        }

        /// <summary>
        ///     Gets a value that indicates whether the dialog is visible. true if the dialog is visible; otherwise, false.
        /// </summary>
        public bool IsOpen
        {
            get { return _isOpen; }
            protected set
            {
                if (value.Equals(_isOpen)) return;
                _isOpen = value;
                OnPropertyChanged("IsOpen");
            }
        }

        /// <summary>
        ///     Gets the dialog result value.
        /// </summary>
        public bool? OperationResult
        {
            get { return _operationResult; }
            set
            {
                if (value.Equals(_operationResult)) return;
                _operationResult = value;
                OnPropertyChanged("OperationResult");
            }
        }

        /// <summary>
        ///     Gets the <see cref="IView" />.
        /// </summary>
        public IWindowViewBase View
        {
            get { return DialogView; }
        }

        /// <summary>
        ///     Gets the command that when invoked, attempts to close dialog.
        /// </summary>
        public virtual ICommand CloseCommand
        {
            get { return _closeableViewModelFacade.CloseCommand; }
            set { _closeableViewModelFacade.CloseCommand = value; }
        }

#if TASKSUPPORT
    /// <summary>
    ///     Tries to close view-model.
    /// </summary>
    /// <param name="parameter">The specified parameter, if any.</param>
    /// <returns>An instance of task with result.</returns>
        public virtual Task<bool> Close(object parameter = null)
        {
            EnsureIsNotDisposed();
            if (DialogView == null)
                Thrower.DialogIsClosed();
            Task<bool> closeTask = _closeableViewModelFacade.Close(parameter);
            return closeTask
                .ContinueWith(task =>
                {
                    if (!task.Result)
                        return false;
                    SafeCloseView();
                    return true;
                }, closeTask.GetContinuationOptions());
        }
#else
        /// <summary>
        ///     Tries to close view-model.
        /// </summary>
        /// <param name="parameter">The specified parameter, if any.</param>
        /// <returns>An instance of operation with result.</returns>
        public virtual IOperationResult<bool> Close(object parameter)
        {
            EnsureIsNotDisposed();
            if (DialogView == null)
                Thrower.DialogIsClosed();
            var operationResult = new ManualOperationResult<bool>();
            _closeableViewModelFacade
                .Close(parameter)
                .BeginWait(result =>
                {
                    try
                    {
                        if (!result.Result)
                        {
                            operationResult.SetResult(false);
                            return;
                        }
                        ThreadManager.InvokeInUi(() =>
                        {
                            SafeCloseView();
                            operationResult.SetResult(true);
                        });
                    }
                    catch (Exception exception)
                    {
                        operationResult.SetException(exception);
                    }
                });
            return operationResult;
        }
#endif

        /// <summary>
        ///     Called when <see cref="ICloseableViewModel" /> removing from the UI Workspace.
        /// </summary>
        public event ViewModelClosingDelegate Closing
        {
            add { _closeableViewModelFacade.Closing += value; }
            remove { _closeableViewModelFacade.Closing -= value; }
        }

        /// <summary>
        ///     Called when <see cref="ICloseableViewModel" /> removed from the UI Workspace.
        /// </summary>
        public event ViewModelClosedDelegate Closed
        {
            add { _closeableViewModelFacade.Closed += value; }
            remove { _closeableViewModelFacade.Closed -= value; }
        }

        #endregion

        #region Properties

        /// <summary>
        ///     Gets the current dialog view.
        /// </summary>
        protected internal T DialogView { get; set; }

        #endregion

        #region Methods

        /// <summary>
        ///     Occurred on closing dialog-view.
        /// </summary>
        protected virtual void OnViewClosing(object sender, CancelEventArgs e)
        {
            EnsureIsNotDisposed();
            try
            {
                _cancelArgs = e;
                CloseFromView(e);
            }
            finally
            {
                _cancelArgs = null;
            }
        }

        /// <summary>
        ///     Configures a view model before show dialog. If you create custom dialog-view model you must call this method before
        ///     show the dialog.
        /// </summary>
        /// <param name="onClosed">The specified callback which will execute after close current dialog.</param>
        /// <param name="viewNameBinding">The specified name of view binding.</param>
        protected virtual void ConfigureBeforeShow(ResultCallback<bool> onClosed = null, string viewNameBinding = null)
        {
            EnsureIsNotDisposed();
            if (IsOpen)
                Thrower.DialogIsOpened();

            var viewManager = Get<IViewManager>();
            T oldView = DialogView;
            DialogView = viewManager.GetView(this, viewNameBinding) as T;
            if (DialogView == null)
                Thrower.ViewNotFound(ViewModelType, typeof(T));
// ReSharper disable once AssignNullToNotNullAttribute
            viewManager.InitializeView(this, DialogView);
            InitializeView(DialogView);
            _onClosed = onClosed;
            IsOpen = true;
            OnViewChanged(oldView, DialogView);
            OnPropertyChanged("View");
        }

        /// <summary>
        ///     Closes the dialog.
        /// </summary>
        protected void SafeCloseView()
        {
            ThreadManager.InvokeInUi(() =>
            {
                if (_cancelArgs != null)
                {
                    _cancelArgs.Cancel = false;
                    CloseInternal();
                    return;
                }
                if (DialogView == null)
                    return;
                _shouldClose = true;
                DialogView.Close();
            });
        }

        /// <summary>
        ///     Occurs when an <see cref="IView" /> for the current view-model changed.
        /// </summary>
        /// <param name="oldView">
        ///     The old value of <see cref="IView" />.
        /// </param>
        /// <param name="newView">
        ///     The new value of <see cref="IView" />.
        /// </param>
        protected virtual void OnViewChanged(T oldView, T newView)
        {
        }

        /// <summary>
        ///     Determines whether the specified command <c>CloseCommand</c> can be execute.
        /// </summary>
        /// <param name="cmdParameter">The specified command parameter.</param>
        /// <returns>
        ///     If <c>true</c> - can execute, otherwise <c>false</c>.
        /// </returns>
        protected virtual bool CanClose(object cmdParameter)
        {
            return true;
        }

#if TASKSUPPORT
    /// <summary>
    ///     Determines whether the specified request can be accepted.
    /// </summary>
    /// <returns>
    ///     If <c>true</c> - close, otherwise <c>false</c>.
    /// </returns>
        protected virtual Task<bool> OnClosing(object parameter)
        {
            return MvvmUtils.TrueTaskResult;
        }
#else
        /// <summary>
        ///     Determines whether the specified request can be accepted.
        /// </summary>
        /// <returns>
        ///     If <c>true</c> - close, otherwise <c>false</c>.
        /// </returns>
        protected virtual IOperationResult<bool> OnClosing(object parameter)
        {
            return MvvmUtils.TrueOperationResult;
        }
#endif

        /// <summary>
        ///     Occurs when dialog-view closed.
        /// </summary>
        protected virtual void OnClosed(object parameter)
        {
        }

        /// <summary>
        ///     Initializes the specified dialog view.
        /// </summary>
        /// <param name="windowView">The specified window-view to initialize.</param>
        protected abstract void InitializeView(T windowView);

        /// <summary>
        ///     Clears the specified dialog view.
        /// </summary>
        /// <param name="windowView">The specified window-view to dispose.</param>
        protected abstract void CleanupView(T windowView);

        /// <summary>
        ///     Clears the current dialog-view.
        /// </summary>
        private void ClearView()
        {
            if (DialogView == null) return;
            CleanupView(DialogView);
            if (DialogView == null) return;
            Get<IViewManager>().CleanupView(this, DialogView);
            DialogView = null;
        }

        private void CloseFromView(CancelEventArgs e)
        {
            if (_shouldClose)
            {
                e.Cancel = false;
                CloseInternal();
                return;
            }
            e.Cancel = true;
#if TASKSUPPORT
            Task<bool> closeTask = Close(null);
            closeTask
                .ContinueWith(task =>
                                   {
                                       if (task.Result)
                                           SafeCloseView();
                                   }, closeTask.GetContinuationOptions())
                .ContinueWith(task =>
                              {
                                  if (_onClosed != null)
                                      _onClosed(task.Exception, false);
                              }, TaskContinuationOptions.OnlyOnFaulted);
#else
            Close(null)
                .BeginWait(result =>
                {
                    if (result.IsFaulted)
                    {
                        if (_onClosed != null)
                            _onClosed(result.Exception, false);
                        return;
                    }
                    if (result.Result)
                        SafeCloseView();
                });
#endif
        }

        private void CloseInternal()
        {
            _shouldClose = false;
            IsOpen = false;
            ClearView();
            if (_onClosed == null) return;
            _onClosed(null, OperationResult.GetValueOrDefault(false));
            _onClosed = null;
        }

        #endregion

        #region Overrides of ViewModelBase

        /// <summary>
        ///     Occurs after the initialization of the current <see cref="ViewModelBase" />.
        /// </summary>
        internal override void OnInitializedInternal()
        {
            _closeableViewModelFacade.ThreadManager = ThreadManager;
            base.OnInitializedInternal();
        }

        /// <summary>
        ///     Occurs after current view model disposed, use for clear resource and event listeners(Internal only).
        /// </summary>
        internal override void OnDisposeInternal(bool disposing)
        {
            if (disposing)
            {
                ClearView();
                _closeableViewModelFacade.Dispose();
            }
            base.OnDisposeInternal(disposing);
        }

        #endregion
    }
}